<template>
  <div class="room-view">
    <header class="room-view__header">
      <h2>这是 {{ id }} 号房间</h2>
    </header>
    <div v-if="loading" class="room-view__loading"></div>
    <div v-else-if="hasRoom" class="room-view__container">
      <TransitionFade>
        <RoomGame
          key="room-game"
          v-if="isGaming"
          :room="roomData"
          @roomExitClicked="handleLeaveRoom"
        />
        <RoomWait
          v-else
          key="room-wait"
          :room="roomData"
          @roomLeaveClicked="handleLeaveRoom"
          @gameStartClicked="handleStartGame"
        />
      </TransitionFade>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from '@/plugins/class-component-hooks'
import {
  Room,
  RoomSetting,
  RoomStateChangedSubscription,
  RoomQuery,
  Status,
  GameMode,
  Difficulty,
  RoomSettingInput,
  PlayerStateUpdatedSubscription,
  RoomStatus
} from '../types'

import {
  VueApolloComponentOptions,
  VueApolloQueryProperty,
  VueApolloQueryDefinition
} from 'vue-apollo/types/options'
import { playerModule } from '@/store'
import { Watch } from 'vue-property-decorator'

import RoomWait from '@/components/RoomWait.vue'
import RoomGame from '@/components/RoomGame.vue'
import { RoomData } from '../types/room'
import { Route } from 'vue-router'
import { CallOnce } from '@/util/util.ts'
import { gqlErrorHandler } from '../util/errorHandler'

@Component({
  components: {
    RoomWait,
    RoomGame
  }
})
export default class RoomView extends Vue {
  @Prop() id!: string
  private room!: RoomQuery['room']
  // roomData will init when room first updated
  //@ts-ignore
  private roomData: RoomData = null

  unmounted() {
    this.handleLeaveRoom()
  }

  beforeRouteLeave(to: Route, from: Route, next: () => void) {
    if (this.room === undefined) {
      next()
      return
    }
    this.handleLeaveRoom()
    next()
  }

  //@ts-ignore
  beforeRouteEnter(to, from, next) {
    next((vm: RoomView) => {
      //@ts-ignore
      vm.roomData = null
      // refetch room
      vm.$apollo.queries.room
        .refetch()
        .catch((err: any) => gqlErrorHandler(vm.$root, err))
    })
  }

  constructor() {
    super()
  }

  @Watch('room')
  onRoomUpdated() {
    if (this.roomData === null) {
      this.onRoomPlayersUpdated()
    } else {
      this.roomData = {
        ...this.room,
        host: this.roomData.host,
        me: this.roomData.me,
        playersMap: this.roomData.playersMap
      }
    }
  }

  @Watch('room.players', { deep: true })
  onRoomPlayersUpdated() {
    if (!this.room) {
      return
    }
    let playersMap: RoomData['playersMap'] = new Map()
    playersMap.clear()
    this.room.players.forEach(player => {
      playersMap.set(player.id, player)
    })

    this.roomData = {
      ...this.room,
      host: playersMap.get(this.room.host.id) as RoomData['host'],
      me: playersMap.get(playerModule.player.id.toString()) as RoomData['me'],
      playersMap: playersMap
    }
  }

  get loading() {
    return this.$apollo.queries.room.loading
  }

  get hasRoom() {
    return this.roomData !== null && this.roomData.me !== undefined
  }

  get isGaming() {
    if (!this.roomData) {
      return false
    }
    return this.room.status == RoomStatus.Gaming
  }

  async leaveRoom() {
    await this.$apollo.mutate({
      mutation: require('@/graphql/leaveRoom.gql')
    })

    this.$router.replace({
      name: 'room-list'
    })
  }

  async startGame() {
    await this.$apollo.mutate({
      mutation: require('@/graphql/startgame.gql')
    })
  }

  @CallOnce()
  handleLeaveRoom() {
    this.leaveRoom().catch((err: Error) => {
      this.$Message.error(err.message)
    })
  }

  handleStartGame() {
    if (this.room.host.id !== playerModule.player.id.toString()) {
      // ignore
      this.$Message.warning('你不是房主，无法开始游戏:)')
      return
    }

    this.startGame().catch((err: Error) => {
      this.$Message.error(err.message)
    })
  }

  get apollo(): Record<string, VueApolloQueryDefinition> {
    return {
      room: {
        query: require('@/graphql/roomquery.gql'),
        variables(this: RoomView) {
          return {
            id: this.id
          }
        },
        subscribeToMore: [
          {
            document: require('@/graphql/roomStateChanged.gql'),
            updateQuery(this: RoomView, previous, { subscriptionData }) {
              let data: RoomStateChangedSubscription['roomStateChanged'] =
                subscriptionData.data.roomStateChanged
              this.room.setting = data.setting
            },
            onError: (err: any) => {
              gqlErrorHandler(this, err)
            }
          },
          {
            document: require('@/graphql/playerStateUpdated.gql'),
            updateQuery(this: RoomView, previous, { subscriptionData }) {
              let data: PlayerStateUpdatedSubscription['playerStatesUpdated'] =
                subscriptionData.data.playerStatesUpdated
              let idx = this.room.players.findIndex(player => {
                return player.id == data.playerID
              })

              if (idx == -1) {
                return
              }
              this.room.players[idx].state = data
            },
            onError: (err: any) => {
              gqlErrorHandler(this, err)
            }
          }
        ]
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.room-view {
  &__header {
    margin-bottom: 1rem;
  }
}
</style>
